#include "engine.h"
#include <iostream>
#include <stdarg.h>

// engine.c
// All core engine code
// Copyright (C)1997 BJ Eirich

// Useflag values:   0 for nonusable.
//                   1 for usable once, then it goes away.
//                   2 for usable an infinite number of times.
//                   3 for usable ONLY camped, only once.
//                   4 for usable ONLY camped, infinitely
//                   5 for usable in combat only, once, then it goes away.
//                   6 for usable in combat only, indefinetly.

// Itemtype values:  0 for OS. (Own Self)
//                   1 for 1A. (One Ally)
//                   2 for AA. (All Allies)
//                   3 for 1E. (One Enemy)
//                   4 for AE. (All Enemy)

char lastmoves[6];                     // the party leader's last six moves
char partyidx[5];                      // current character-ref indexes
char numchars = 0;                       // number of characters to draw
struct vspanim va0[100];               // tile animation data
struct r_entity party[101];            // party entities
struct zoneinfo zone[128];             // entity records
struct charstats party_stats[30];           // party-stats record
struct itemdesc items[255];            // item definition array

struct magicdesc magic[255];
unsigned char* magicicons;        // more graphic ptrs
struct mequipstruc mequip[255];          // equipment definition array

// END NEW

struct equipstruc equip[255];          // equipment definition array
char zonedelay;
char lz;                     // zone delay counter
char tchars;                           // total characters in PARTY.DAT
int gp;                                // party's gold (or meseta, s, $, etc)

unsigned short vadelay[100];       // delay counter
unsigned short* map0;
unsigned short* map1;        // map data pointers
unsigned char* vsp0;
unsigned char* mapp;
unsigned char* chrs;       // graphic data pointers
unsigned char* itemicons;
unsigned char* chr2;        // more graphic ptrs
unsigned int flags[8000];              // misc flags

FILE* map;
FILE* vsp;                        // file handles
char mapname[13];
char musname[13];         // map/music filenames
char vsp0name[13];
char levelname[30];      // vsp filename / level name
char layerc;
char showname;
char saveflag;         // control flags

/* -- ric: 21/Apr/98 - moved to process_controls -- */
// char l,r,t,b;                          // wall status

int xwin;
int ywin;                        // window coordinates
int xtc;
int ytc;
int xofs;
int yofs;                 // variables for drawmap()
unsigned short xsize;
unsigned short ysize;        // x/y map dimensions
unsigned short startx;
unsigned short starty;      // default start location
unsigned char pmultx;
unsigned char pdivx;            // parallax speed X
unsigned char pmulty;
unsigned char pdivy;            // parallax speed X
char warp;
char hide;                       // map warpable / hidable flags

char dontshowname = 0;
char usenxy = 0;
char qabort = 0;
char autoent = 0;
unsigned short nx = 0;
unsigned short ny = 0;
unsigned short numtiles;

struct mstruct monsters[5];

char** mlist;

// END REVISION 2 ADDITIONS

namespace VERGE1 {
    
    Engine engine;

    Engine::Engine() {
        // empty
    }
    
    Engine::~Engine() {
        // empty
    }
    
    File* Engine::fopen(const char* filename, const char* mode) {
        std::cout << "Engine::fopen " << filename << std::endl;
        std::FILE* stream = std::fopen(filename, mode);
        return stream ? new File(stream) : 0;
    }
    
    int Engine::fclose(File*& file) {
        std::cout << "Engine::fclose" << std::endl;
        int result = std::fclose(file->stream);
        delete file;
        file = 0;
        return result;
    }
    
    int Engine::fscanf(File* file, const char* format, ...) {
        std::cout << "Engine::fscanf" << std::endl;
        va_list list;
        int done;
        
        va_start(list, format);
        done = std::fscanf(file->stream, format, list);
        va_end(list);
        
        return done;
    }
    
    char* Engine::fgets(char* s, int n, File* file) {
        std::cout << "Engine::fgets" << std::endl;
        return std::fgets(s, n, file->stream);
    }

    int Engine::fwrite(const void* p, int size, int count, File* file) {
        std::cout << "Engine::fwrite" << std::endl;
        return std::fwrite(p, size, count, file->stream);
    }

    int Engine::remove(const char* filename) {
        std::cout << "Engine::remove" << std::endl;
        return std::remove(filename);
    }
    void Engine::InitPartyStats() {
        if (!strbuf) {
            err("strbuf is not allocated");
        }
        
        File* file = fopen("PARTY.DAT", "r");
        if (!file) {
            err("Fatal error: PARTY.DAT not found");
        }
        
        fscanf(file, "%s", strbuf);
        tchars = atoi(strbuf);
        
        for (int i = 0; i < tchars; i++) {
            fscanf(file, "%s", party_stats[i].chrfile);
            fscanf(file, "%s", strbuf);
            fscanf(file, "%s", strbuf);
        
            File* cdat = fopen(strbuf, "r");
            if (!cdat) {
                err("Could not open character DAT file.");
            }
            
            fscanf(cdat, "%s", party_stats[i].name);
            
            fgets(strbuf, 99, cdat);
            fgets(strbuf, 99, cdat);
            
            fscanf(cdat, "%s", strbuf);
            party_stats[i].exp = atoi(strbuf);
            
            fscanf(cdat, "%s", strbuf);
            party_stats[i].curhp = atoi(strbuf);
            party_stats[i].maxhp = atoi(strbuf);
            
            fscanf(cdat, "%s", strbuf);
            party_stats[i].curmp = atoi(strbuf);
            party_stats[i].maxmp = atoi(strbuf);
            
            fscanf(cdat, "%s", strbuf);
            party_stats[i].str = atoi(strbuf);
            
            fscanf(cdat, "%s", strbuf);
            party_stats[i].end = atoi(strbuf);
            
            fscanf(cdat, "%s", strbuf);
            party_stats[i].mag = atoi(strbuf);
            
            fscanf(cdat, "%s", strbuf);
            party_stats[i].mgr = atoi(strbuf);
            
            fscanf(cdat, "%s", strbuf);
            party_stats[i].hit = atoi(strbuf);
            
            fscanf(cdat, "%s", strbuf);
            party_stats[i].dod = atoi(strbuf);
            
            fscanf(cdat, "%s", strbuf);
            party_stats[i].mbl = atoi(strbuf);
            
            fscanf(cdat, "%s", strbuf);
            party_stats[i].fer = atoi(strbuf);
            
            fscanf(cdat, "%s", strbuf);
            party_stats[i].rea = atoi(strbuf);
            party_stats[i].lv = 1;
    
            fscanf(cdat, "%s", strbuf);
            party_stats[i].nxt = atoi(strbuf);
            party_stats[i].status = 0;
            party_stats[i].invcnt = 6;
            
            memset(&party_stats[i].inv, 0, 512);
            
            fclose(cdat);
        }
    
        fclose(file);
    }

} // VERGE1 namespace

// ============================ Code ============================

void allocbuffers() {
    map0 = (unsigned short*)malloc(mapm * 2);
    map1 = (unsigned short*)malloc(mapm * 2);
    mapp = (unsigned char*)malloc(mapm);
    vsp0 = (unsigned char*)malloc(vspm);
    chrs = (unsigned char*)malloc(512000);
    chr2 = (unsigned char*)malloc(46080);

    magicicons = (unsigned char*)malloc(50688);
    memset(magicicons, 0, 50688);

    itemicons = (unsigned char*)malloc(50688);
    speech = (unsigned char*)malloc(450000);
    msbuf = (char*)malloc(20000);
    mlist = (char**)malloc(3315);

    memset(map0, 0, mapm * 2);
    memset(map1, 0, mapm * 2);
    memset(mapp, 0, mapm);
    memset(vsp0, 0, vspm);
    memset(chrs, 0, 512000);
    memset(chr2, 0, 46080);
    memset(speech, 0, 450000);
    memset(msbuf, 0, 20000);

    memset(flags, 0, 32000);
    memset(itemicons, 0, 50688);
    memset(&party, 0, sizeof party);

    InitVCMem(mapvcm);
}

void addcharacter(int i)
{ FILE *chrf,*p;
  int b;

  numchars++;
  partyidx[numchars-1]=i;
  p=fopen("PARTY.DAT","r");
  if (!p) err("Fatal error: PARTY.DAT not found");
  fscanf(p,"%s",strbuf);
  tchars=atoi(strbuf);
  if (i>atoi(strbuf)) err("addcharacter(): index out of range");
  for (b=1;b<i;b++)
      { fscanf(p,"%s",strbuf);
        fscanf(p,"%s",strbuf);
        fscanf(p,"%s",strbuf); }
  fscanf(p,"%s",strbuf);

  party[0].chrindex=0;

  party[1].x=party[0].x;
  party[1].y=party[0].y;
  party[1].cx=party[0].cx;
  party[1].cy=party[0].cy;
  party[1].facing=0;
  party[1].moving=0;
  party[1].chrindex=1;

  party[2].x=party[0].x;
  party[2].y=party[0].y;
  party[2].cx=party[0].cx;
  party[2].cy=party[0].cy;
  party[2].facing=0;
  party[2].moving=0;
  party[2].chrindex=2;

  party[3].x=party[0].x;
  party[3].y=party[0].y;
  party[3].cx=party[0].cx;
  party[3].cy=party[0].cy;
  party[3].facing=0;
  party[3].moving=0;
  party[3].chrindex=3;

  party[4].x=party[0].x;
  party[4].y=party[0].y;
  party[4].cx=party[0].cx;
  party[4].cy=party[0].cy;
  party[4].facing=0;
  party[4].moving=0;
  party[4].chrindex=4;

  lastmoves[0]=0; lastmoves[1]=0; lastmoves[2]=0;
  lastmoves[3]=0; lastmoves[4]=0; lastmoves[5]=0;
  chrf=fopen(party_stats[i-1].chrfile,"rb");
  if (!chrf) err("addcharacter(): CHR file not found");
  fread(chrs+((numchars-1)*15360), 1, 15360, chrf);
  fclose(chrf);

  fscanf(p,"%s",strbuf);
  fclose(p);
  chrf=fopen((char*)strbuf,"rb");
  if (!chrf) err("addcharacter(): CR2 file not found");
  fread(chr2+((numchars-1)*9216), 1, 9216, chrf);
  fclose(chrf);
}

void LoadCHRList() {
    int i;
    FILE* f;

    for (i = 0; i < 20; i++) {
        if (strlen(chrlist[i].fname)) {
            f = fopen(chrlist[i].fname, "rb");
            fread(chrs + ((i + 5) * 15360), 30, 512, f);
            fclose(f);
        }
    }
}

void load_map(char *fname)
{ unsigned char b;
  int i;

  memcpy(&mapname,fname,13);
  map=fopen(fname,"rb");
  if (!map) err("Could not open specified MAP file.");
  fread(&b, 1, 1, map);
  if (b!=4) err("*error* Incorrect MAP version.");
  fread(&vsp0name, 1, 13, map);
  fread(&musname, 1, 13, map);
  fread(&layerc, 1, 1, map);
  fread(&pmultx, 1, 1, map); pmulty=pmultx;
  fread(&pdivx, 1, 1, map); pdivy=pdivx;
  fread(&levelname, 1, 30, map);
  fread(&showname, 1, 1, map);
  fread(&saveflag, 1, 1, map);
  fread(&startx, 1, 2, map);
  fread(&starty, 1, 2, map);
  fread(&hide, 1, 1, map);
  fread(&warp, 1, 1, map);
  fread(&xsize, 1, 2, map);
  fread(&ysize, 1, 2, map);
  fread(&b, 1, 1, map);
  if (b) err("*error* MAP compression not yet supported.");

  fread(map0, 1, 27, map);              // skip buffer

  fread(map0, xsize, ysize*2, map);
  fread(map1, xsize, ysize*2, map);
  fread(mapp, xsize, ysize, map);

  fread(&zone, 1, sizeof zone, map);
  fread(&chrlist, 13, 100, map);

  LoadCHRList();

  fread(&entities, 1, 4, map);
  fread(&party[5], 88, entities, map);

  for (i=5; i<entities+5; i++)
  {
    party[i].cx=party[i].x;
    party[i].cy=party[i].y;
    party[i].x=party[i].x*16;
    party[i].y=party[i].y*16;
  }

  fread(&nummovescripts, 1, 1, map);
  fread(&i, 1, 4, map);
  fread(&msofstbl, 4, nummovescripts, map);
  fread(msbuf, 1, i, map);

  entities+=5;

  LoadVC(map);
  fclose(map);

  if (strlen(musname)) playsong(musname);

  vsp=fopen(vsp0name,"rb");
  if (!vsp) err("Could not open specified VSP file.");
  fseek(vsp, 2, 0);
  fread(&pal, 1, 768, vsp);
  fread(&numtiles, 1, 2, vsp);
  fread(vsp0, 1, (256*numtiles), vsp);
  fread(&va0, 1, sizeof va0, vsp);
  fclose(vsp);

  for (i=0; i<2048; i++)
      tileidx[i]=i;

  if (vspspeed) CalcVSPMask();

  if (usenxy) { startx=nx; starty=ny; }

  party[0].x=startx*16;
  party[0].y=starty*16;
  party[0].moving=0;
  party[0].cx=startx;
  party[0].cy=starty;

  party[1].x=party[0].x;
  party[1].y=party[0].y;
  party[1].moving=0;
  party[1].cx=startx;
  party[1].cy=starty;

  party[2].x=party[0].x;
  party[2].y=party[0].y;
  party[2].moving=0;
  party[2].cx=startx;
  party[2].cy=starty;

  party[3].x=party[0].x;
  party[3].y=party[0].y;
  party[3].moving=0;
  party[3].cx=startx;
  party[3].cy=starty;

  party[4].x=party[0].x;
  party[4].y=party[0].y;
  party[4].moving=0;
  party[4].cx=startx;
  party[4].cy=starty;

  lastmoves[0]=0; lastmoves[1]=0; lastmoves[2]=0;
  lastmoves[3]=0; lastmoves[4]=0; lastmoves[5]=0;

  ExecuteScript(0);
}

void process_stepzone()
{ // Called each time the player steps in a new square. Looks up current zone
  // information, adjusts for delays and then makes a chance roll to see if
  // an event is called.

  unsigned char cz, t1;

  cz=mapp[(((party[0].y/16)*xsize)+(party[0].x/16))] >> 1;
  if (lz!=cz) { zonedelay=0; lz=cz; }
  if (!zone[cz].percent) return;
  if (zonedelay<zone[cz].delay)
     { zonedelay++; return; }

  t1=(rand()*255);
  if (t1<=zone[cz].percent)
  {
      ExecuteScript(zone[cz].callevent);
      zonedelay=0;
  }
}

void lastmove(char n)
{ // moves all the entrees in the lastmoves array down one, then places the
  // new direction at the front.

  lastmoves[5]=lastmoves[4];
  lastmoves[4]=lastmoves[3];
  lastmoves[3]=lastmoves[2];
  lastmoves[2]=lastmoves[1];
  lastmoves[1]=lastmoves[0];
  lastmoves[0]=n;
}

void startfollow()
{ int i;
  for (i=1; i<numchars; i++)
      { if (lastmoves[i]==1)
           { party[i].y++; party[i].facing=0; party[i].cy++;
             party[i].moving=1; party[i].movcnt=15; }
        if (lastmoves[i]==2)
           { party[i].y--; party[i].facing=1; party[i].cy--;
             party[i].moving=2; party[i].movcnt=15; }
        if (lastmoves[i]==3)
           { party[i].x++; party[i].facing=2; party[i].cx++;
             party[i].moving=3; party[i].movcnt=15; }
        if (lastmoves[i]==4)
           { party[i].x--; party[i].facing=3; party[i].cx--;
             party[i].moving=4; party[i].movcnt=15; } }
}

int InvFace()
{
  switch(party[0].facing)
  {
    case 0: return 1;
    case 1: return 0;
    case 2: return 3;
    case 3: return 2;
  }
  // TODO: handle better?
  return -1;
}

void Activate()
{ int tx,ty;
  unsigned char cz,t;

  // First, determine what tile we're looking at.
  tx=party[0].x/16;
  ty=party[0].y/16;
  switch (party[0].facing)
  {
    case 0: { ty++; break; }
    case 1: { ty--; break; }
    case 2: { tx++; break; }
    case 3: { tx--; break; }
  }

  // Here is where we first check for entities in our way.

  t=EntityAt(tx,ty);
  if (t)
     if (!party[t].activmode && party[t].actscript)
     {
        if (party[t].face) party[t].facing=InvFace();
        ExecuteScript(party[t].actscript);
        return;
     }

  // Check adjacent zones.
  cz=mapp[((ty*xsize)+tx)] >> 1;
  if (zone[cz].aaa)
  {
       ExecuteScript(zone[cz].callevent);
       return;
  }
}

int ObstructionAt(int tx,int ty)
{
  if ((mapp[((ty)*xsize)+tx] & 1)==1) return 1;
  return 0;
}

void process_entities()
{ int i;

  for (i=5; i<entities; i++)
    ProcessSpeedAdjEntity(i);

  if (autoent)
     for (i=95; i<95+numchars; i++)
       ProcessSpeedAdjEntity(i);
}

void ProcessControls()
{
  if (party[0].speed<4)
  {
    switch (party[0].speed)
    {
      case 1: if (party[0].speedct<3) { party[0].speedct++; return; }
      case 2: if (party[0].speedct<2) { party[0].speedct++; return; }
      case 3: if (party[0].speedct<1) { party[0].speedct++; return; }
    }
  }
  if (party[0].speed<5) process_controls();
  switch (party[0].speed)
  {
    case 5: process_controls(); process_controls(); return;
    case 6: process_controls(); process_controls(); process_controls(); return;
    case 7: process_controls(); process_controls();
            process_controls(); process_controls(); return;
  }
}

void process_controls()
{ unsigned char i;
  char l,r,t,b;  /* -- ric: 21/Apr/98 - moved from top -- */
  party[0].speedct=0;
  if (!party[0].moving) {
      xtc=party[0].x/16; ytc=party[0].y/16;
      if ((mapp[((ytc+1)*xsize)+xtc] & 1)==1) b=0; else b=1;
      if ((mapp[((ytc-1)*xsize)+xtc] & 1)==1) t=0; else t=1;
      if ((mapp[(ytc*xsize)+((party[0].x+17)/16)] & 1)==1) r=0; else r=1;
      if ((mapp[(ytc*xsize)+((party[0].x-15)/16)] & 1)==1) l=0; else l=1;

     if (xtc==0) l=0;
     if (ytc==0) t=0;
     if (xtc==xsize-1) r=0; /* -- ric: 28/Apr/98 -- */
     if (ytc==ysize-1) b=0; /* -- ric: 28/Apr/98 -- */

     readcontrols();
     if (b1) Activate();
     if (b3) SystemMenu();
     if (b4) MainMenu();

     for(i=0;i<128;i++) {   /* -- ric: 30/May/98 -- */
       if ((key_map[i].pressed) && (key_map[i].boundscript)) {
//         dec_to_asciiz(i,strbuf);
//         err(strbuf);
         ExecuteStartUpScript(key_map[i].boundscript);
       }
     }

     xtc=party[0].x/16; ytc=party[0].y/16;

     if (right) party[0].facing=2;
     if (down) party[0].facing=0;
     if (left) party[0].facing=3;
     if (up) party[0].facing=1;

     if ((right) && (r) && !EntityAt(xtc+1,ytc))
        { party[0].x++; party[0].facing=2; party[0].moving=3; party[0].cx++;
          party[0].movcnt=15; lastmove(3); startfollow(); return; }
     if ((down) && (b) && !EntityAt(xtc,ytc+1))
        { party[0].y++; party[0].facing=0; party[0].moving=1; party[0].cy++;
          party[0].movcnt=15; lastmove(1); startfollow(); return; }
     if ((left) && (l) && !EntityAt(xtc-1,ytc))
        { party[0].x--; party[0].facing=3; party[0].moving=4; party[0].cx--;
          party[0].movcnt=15; lastmove(4); startfollow(); return; }
     if ((up) && (t) && !EntityAt(xtc,ytc-1))
        { party[0].y--; party[0].facing=1; party[0].moving=2; party[0].cy--;
          party[0].movcnt=15; lastmove(2); startfollow(); return; }

     party[0].framectr=0; party[1].framectr=0; party[2].framectr=0;
     party[3].framectr=0; party[4].framectr=0; }

  if (party[0].moving) {
  for (i=0; i<numchars; i++) {
      if (party[i].moving==1)
         { party[i].y++; party[i].movcnt--; party[i].framectr++; }
      if (party[i].moving==2)
         { party[i].y--; party[i].movcnt--; party[i].framectr++; }
      if (party[i].moving==3)
         { party[i].x++; party[i].movcnt--; party[i].framectr++; }
      if (party[i].moving==4)
         { party[i].x--; party[i].movcnt--; party[i].framectr++; }
    if (party[i].framectr==80) party[i].framectr=0; }
  if (!party[0].movcnt)
     { party[0].moving=0; process_stepzone(); }
  }
}

void check_tileanimation()
{ unsigned char i;

  for (i=0; i<100; i++)
      { if ((va0[i].delay) && (va0[i].delay<vadelay[i]))
           animate(i);
        vadelay[i]++; }
}

void UpdateEquipStats()
{ int i,j,a;

  // This function takes the base stats of all characters and re-calculates
  // current-stats by re-applying equipment modifiers.

  for (i=0; i<30; i++)
  {
     party_stats[i].atk=party_stats[i].str;
     party_stats[i].def=party_stats[i].end;
     party_stats[i].magc=party_stats[i].mag;
     party_stats[i].mgrc=party_stats[i].mgr;
     party_stats[i].hitc=party_stats[i].hit;
     party_stats[i].dodc=party_stats[i].dod;
     party_stats[i].mblc=party_stats[i].mbl;
     party_stats[i].ferc=party_stats[i].fer;
     party_stats[i].reac=party_stats[i].rea;

     for (j=0; j<6; j++)
     {
        a=items[party_stats[i].inv[j]].equipidx;
        party_stats[i].atk+=equip[a].str;
        party_stats[i].def+=equip[a].end;
        party_stats[i].magc+=equip[a].mag;
        party_stats[i].mgrc+=equip[a].mgr;
        party_stats[i].hitc+=equip[a].hit;
        party_stats[i].dodc+=equip[a].dod;
        party_stats[i].mblc+=equip[a].mbl;
        party_stats[i].ferc+=equip[a].fer;
        party_stats[i].reac+=equip[a].rea;
     }
  }
}

void game_ai()
{
  ProcessControls();
  process_entities();
  if (keyboard_map[SCAN_CTRL] && speed) ProcessControls();
  if (moneycheat) gp=100000;
  check_tileanimation();
}

void CreateSaveImage(unsigned char *buf)
{
  memcpy(buf,chrs,512);
  if (numchars>1) memcpy(buf+512,chrs+15360,512);
             else memset(buf+512,0,512);
  if (numchars>2) memcpy(buf+1024,chrs+30720,512);
             else memset(buf+1024,0,512);
  if (numchars>3) memcpy(buf+1536,chrs+46080,512);
             else memset(buf+1536,0,512);
  if (numchars>4) memcpy(buf+2048,chrs+61440,512);
             else memset(buf+2048,0,512);
}

void SaveGame(char *fn)
{ FILE *f;
  unsigned char cz;
  unsigned char temp[2560];

  f=fopen(fn,"wb");
  cz=mapp[(((party[0].y/16)*xsize)+(party[0].x/16))] >> 1;
  fwrite(&zone[cz].savedesc, 1, 30, f);
  cz=partyidx[0]-1;
  fwrite(&party_stats[cz].name, 1, 9, f);
  fwrite(&party_stats[cz].lv, 1, 4, f);
  fwrite(&party_stats[cz].curhp, 1, 8, f);
  fwrite(&gp, 1, 4, f);
  fwrite(&hr, 1, 1, f);
  fwrite(&min, 1, 1, f);
  fwrite(&sec, 1, 1, f);
  fwrite(&numchars, 1, 1, f);
  fwrite(&menuactive, 1, 1, f);
  CreateSaveImage(temp);
  fwrite(&temp, 1, 2560, f);
  fwrite(&mapname, 1, 13, f);
  fwrite(&party, 1, sizeof party, f);
  fwrite(&partyidx, 1, 5, f);
  fwrite(&flags, 1, 32000, f);
  fwrite(&tchars, 1, 1, f);
  fwrite(&party_stats, 1, sizeof party_stats, f);
  fclose(f);
}

void startmap(char *fname)
{
  if (qabort) return;

  set_intensity(0);
  load_map(fname);
  drawmap();
  vgadump();
  fin();

  timer_count=0; lz=0;
  tickctr=0; zonedelay=0;

main_loop:
  while (timer_count!=0)
        { timer_count--;
          game_ai(); }

  drawmap();
  vgadump();

  while (!timer_count) { gp--; gp++; }

  if (!qabort) goto main_loop;
}
